﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SQLite;
using System.IO;
using System.Text;
//using Mono.Data.Sqlite;

using static Apewer.Source.SourceUtility;

namespace Apewer.Source
{

    /// <summary>连接 SQLite 数据库的客户端。</summary>
    /// <remarks>Require System.Data.SQLite.Core v1.0.110</remarks>
    public class Sqlite : DbClient
    {

        #region connection

        private SQLiteConnection _conn = null;
        private string _connstr = null;
        private string _path = null;
        private string _pass = null;

        /// <summary>连接字符串。</summary>
        public override string ConnectionString => _connstr;

        /// <summary>当前数据库的文件路径。</summary>
        public string Path { get => _path; }

        /// <summary>使用现有的连接创建实例。</summary>
        /// <param name="connection">有效的 SQLite 连接。</param>
        /// <param name="timeout">超时设定。</param>
        /// <exception cref="ArgumentNullException"></exception>
        /// <exception cref="ArgumentException"></exception>
        public Sqlite(IDbConnection connection, Timeout timeout = null) : base(timeout)
        {
            if (connection == null) throw new ArgumentNullException(nameof(connection), "指定的连接无效。");

            var conn = connection as SQLiteConnection;
            if (conn == null) throw new ArgumentException(nameof(connection), "指定的连接不是支持的类型。");

            _conn = conn;
            _connstr = conn.ConnectionString;
        }

        /// <summary>创建连接实例。</summary>
        /// <param name="path">数据库的文件路径，指定为空时将使用 :memory: 作为路径。</param>
        /// <param name="pass">连接数据库的密码，使用内存数据库时此参数将被忽略。</param>
        /// <param name="timeout">超时设定。</param>
        /// <exception cref="FileNotFoundException"></exception>
        /// <exception cref="IOException"></exception>
        /// <exception cref="NotSupportedException"></exception>
        /// <exception cref="PathTooLongException"></exception>
        /// <exception cref="UnauthorizedAccessException"></exception>
        public Sqlite(string path = null, string pass = null, Timeout timeout = null) : base(timeout)
        {
            // 使用内存。
            if (string.IsNullOrEmpty(path) || path.ToLower() == Memory)
            {
                _connstr = "data source=':memory:'; version=3; ";
                _path = Memory;
                return;
            }

            // 使用文件。
            if (!File.Exists(path))
            {
                var dir = Directory.GetParent(path).FullName;
                if (!Directory.Exists(dir)) Directory.CreateDirectory(dir);
                File.Create(path).Dispose();
            }
            _connstr = $"data source='{path}'; version=3; ";
            _path = path;
            if (!string.IsNullOrEmpty(pass))
            {
                pass = pass.Trim();
                if (!string.IsNullOrEmpty(pass))
                {
                    _connstr += $"password={_pass}; ";
                    _pass = pass;
                }
            }
        }

        /// <summary>
        /// <para>挂载数据库。</para>
        /// <para>attach database "<paramref name="path"/>" as "<paramref name="alias"/>"</para>
        /// </summary>
        public string Attach(string path, string alias)
        {
            if (path.IsEmpty()) return "未指定数据库路径。";
            if (alias.IsEmpty()) return "未指定别名。";

            var connect = Connect();
            if (connect.NotEmpty()) return connect;

            var sql = $"attach database \"{path}\" as \"{alias}\"";
            var execute = Execute(sql, null, false) as Execute;
            return execute.Success ? null : execute.Message;
        }

        /// <summary>
        /// <para>卸载数据库。</para>
        /// <para>detach database "<paramref name="alias"/>"</para>
        /// </summary>
        public string Detach(string alias)
        {
            if (alias.IsEmpty()) return "未指定别名。";

            var connect = Connect();
            if (connect.NotEmpty()) return connect;

            var sql = $"detach \"{alias}\"";
            var execute = Execute(sql, null, false) as Execute;
            return execute.Success ? null : execute.Message;
        }

        #endregion

        #region public

        /// <exception cref="NotImplementedException"></exception>
        public override string[] StoreNames() => throw new NotImplementedException();

        /// <summary></summary>
        public override string[] TableNames() => QueryStrings("select name from sqlite_master where type='table' order by name");

        /// <summary></summary>
        public override string[] ColumnNames(string tableName) => QueryStrings($"pragma table_info('{tableName.SafeName()}'); ");

        /// <summary>插入记录。返回错误信息。</summary>
        public override string Insert(object record, string table = null, bool adjust = true)
        {
            if (record == null) return "参数无效。";
            if (adjust) SourceUtility.FixProperties(record);

            // 解析模型，获取表名。
            var structure = TableStructure.Parse(record.GetType());
            if (structure == null) return "无法解析记录模型。";
            if (string.IsNullOrEmpty(table)) table = structure.TableName;
            if (string.IsNullOrEmpty(table)) return "表名称无效。";

            // 排除字段。
            var excluded = new List<string>();
            foreach (var ca in structure.Columns)
            {
                // 排除不使用 ORM 的属性。
                if (ca.Independent || ca.Incremental) excluded.Add(ca.Field);
            }

            // 准备参数。
            var ps = structure.CreateParameters(record, Parameter, excluded);
            var psc = ps.Length;
            if (psc < 1) return "数据模型不包含字段。";

            // 合成 SQL 语句。
            var sb = new StringBuilder();
            sb.Append("insert into ");
            sb.Append(table);
            sb.Append("(");
            for (var i = 0; i < psc; i++)
            {
                if (i > 0) sb.Append(", ");
                sb.Append(ps[i].ParameterName);
            }
            sb.Append(") values(");
            for (var i = 0; i < psc; i++)
            {
                if (i > 0) sb.Append(", ");
                sb.Append("@");
                sb.Append(ps[i].ParameterName);
            }
            sb.Append("); ");
            var sql = sb.ToString();

            // 执行。
            var execute = Execute(sql, ps, false);
            if (execute.Success && execute.Rows > 0) return TextUtility.Empty;
            return execute.Message;
        }

        /// <summary>更新记录，实体中的 Key 属性不被更新。返回错误信息。</summary>
        /// <remarks>无法更新带有 Independent 特性的模型（缺少 Key 属性）。</remarks>
        public override string Update(IRecord record, string table = null, bool adjust = true)
        {
            if (record == null) return "参数无效。";
            if (adjust)
            {
                FixProperties(record);
                SetUpdated(record);
            }

            // 解析模型，获取表名。
            var structure = TableStructure.Parse(record.GetType());
            if (structure == null) return "无法解析记录模型。";
            if (structure.Independent) return "无法更新带有 Independent 特性的模型。";
            if (string.IsNullOrEmpty(table)) table = structure.TableName;
            if (string.IsNullOrEmpty(table)) return "表名称无效。";

            // 排除字段。
            var excluded = new List<string>();
            if (structure.Key != null) excluded.Add(structure.Key.Field);
            foreach (var ca in structure.Columns)
            {
                // 排除不使用 ORM 的属性、自增属性和主键属性。
                if (ca.Independent || ca.Incremental || ca.PrimaryKey || ca.NoUpdate) excluded.Add(ca.Field);
            }

            // 准备参数。
            var ps = structure.CreateParameters(record, Parameter, excluded);
            var psc = ps.Length;
            if (psc < 1) return "数据模型不包含字段。";

            // 合成 SQL 语句。
            var cs = new List<string>();
            foreach (var p in ps) cs.Add($"[{p.ParameterName}] = @{p.ParameterName}");
            var sql = $"update {table} set {string.Join(", ", cs.ToArray())} where [{structure.Key.Field}] = '{record.Key.SafeKey()}'";

            // 执行。
            var execute = Execute(sql, ps, false);
            if (execute.Success && execute.Rows > 0) return TextUtility.Empty;
            return execute.Message;
        }

        #endregion

        #region protected

        /// <summary></summary>
        protected override string Initialize(TableStructure structure, string table)
        {
            if (string.IsNullOrEmpty(table)) table = structure.TableName;

            // 检查现存表。
            var exists = false;
            var tables = TableNames();
            if (tables.Length > 0)
            {
                var lower = table.ToLower();
                foreach (var tn in tables)
                {
                    if (TextUtility.IsEmpty(tn)) continue;
                    if (tn.ToLower() == lower)
                    {
                        exists = true;
                        break;
                    }
                }
            }

            if (exists)
            {
                return TextUtility.Merge("指定的表已经存在。");
            }
            else
            {
                var sqlcolumns = new List<string>();
                foreach (var column in structure.Columns)
                {
                    var type = Declaration(column);
                    if (string.IsNullOrEmpty(type))
                    {
                        return TextUtility.Merge("类型 ", column.Type.ToString(), " 不受支持。");
                    }

                    if (!column.Independent)
                    {
                        if (column.PrimaryKey) type = type + " primary key";
                    }

                    sqlcolumns.Add(type);
                }
                var sql = TextUtility.Merge("create table [", table, "](", TextUtility.Join(", ", sqlcolumns), "); ");
                var execute = Execute(sql, null, false);
                if (execute.Success) return TextUtility.Empty;
                return execute.Message;
            }
        }

        /// <summary></summary>
        protected override IDataAdapter CreateDataAdapter(IDbCommand command) => new SQLiteDataAdapter((SQLiteCommand)command);

        /// <summary></summary>
        protected override IDbConnection GetConnection()
        {
            if (_conn == null) _conn = new SQLiteConnection(_connstr);
            return _conn;
        }

        /// <summary></summary>
        protected override IDataParameter CreateParameter() => new SQLiteParameter();

        /// <summary></summary>
        protected override string Keys(string tableName, string keyField, string flagField, long flagValue)
        {
            if (flagValue == 0) return $"select [{keyField}] from [{tableName}] where [{flagField}] = {flagValue}";
            return $"select [{keyField}] from [{tableName}]";
        }

        /// <summary></summary>
        protected override string Get(string tableName, string keyField, string keyValue, string flagField, long flagValue)
        {
            if (flagValue == 0) return $"select * from [{tableName}] where [{keyField}] = '{keyValue}' limit 1";
            else return $"select * from [{tableName}] where [{keyField}] = '{keyValue}' and [{flagField}] = {flagValue} limit 1";
        }

        /// <summary></summary>
        protected override string List(string tableName, string flagField, long flagValue)
        {
            if (flagValue == 0) return $"select * from [{tableName}]";
            else return $"select * from [{tableName}] where [{flagField}] = {flagValue}";
        }

        #endregion

        #region special

        /// <summary>整理数据库，压缩未使用的空间。</summary>
        public const string Vacuum = "vacuum";

        /// <summary>内存数据库的地址。</summary>
        public const string Memory = ":memory:";

        /// <summary>查询数据库中的所有视图名。</summary>
        public string[] ViewNames() => QueryStrings("select name from sqlite_master where type='view' order by name");

        #endregion

        #region mirror

        /// <summary>保存当前数据库到文件，若文件已存在则将重写文件。</summary>
        public string Save(string path, string pass = null)
        {
            if (path.IsEmpty()) return "参数 path 无效。";
            if (!StorageUtility.CreateFile(path, 0, true))
            {
                var msg = $"创建文件 {path} 失败。";
                Logger.Error(nameof(Sqlite), "Save", msg);
                return msg;
            }
            using (var destination = new Sqlite(path, pass)) return Save(destination);
        }

        /// <summary>保存当前数据库到目标数据库。</summary>
        public string Save(Sqlite destination) => Backup(this, destination);

        /// <summary>加载文件到当前数据库。</summary>
        public string Load(string path, string pass = null)
        {
            using (var source = new Sqlite(path, pass)) return Load(source);
        }

        /// <summary>加载源数据库到当前数据库。</summary>
        public string Load(Sqlite source) => Backup(source, this);

        /// <summary>备份数据库，返回错误信息。</summary>
        string Backup(Sqlite source, Sqlite destination)
        {
            if (source == null) return "备份失败：源无效。";
            if (destination == null) return "备份失败：目标无效。";
            var sConnect = source.Connect();
            if (sConnect.NotEmpty()) return sConnect;
            var dConnect = source.Connect();
            if (dConnect.NotEmpty()) return dConnect;

            try
            {
                var src = (SQLiteConnection)source.Connection;
                var dst = (SQLiteConnection)destination.Connection;
                src.BackupDatabase(dst, "main", "main", -1, null, 0);
                return "";
            }
            catch (Exception ex)
            {
                var msg = "备份失败：" + ex.Message;
                Logger?.Error(this, msg);
                return msg;
            }
        }

        #endregion

        #region type & parameter

        private static string GetColumnTypeName(ColumnType type)
        {
            switch (type)
            {
                case ColumnType.Bytes:
                    return "blob";
                case ColumnType.Integer:
                    return "integer";
                case ColumnType.Float:
                    return "float";
                case ColumnType.VarChar:
                case ColumnType.VarChar191:
                case ColumnType.VarCharMax:
                    return "varchar";
                case ColumnType.Text:
                    return "text";
                case ColumnType.NVarChar:
                case ColumnType.NVarChar191:
                case ColumnType.NVarCharMax:
                    return "nvarchar";
                case ColumnType.NText:
                    return "ntext";
                default:
                    return null;
            }
        }

        private static string Declaration(ColumnAttribute column)
        {
            var type = TextUtility.Empty;
            var length = NumberUtility.Restrict(column.Length, 0, 255).ToString();
            switch (column.Type)
            {
                case ColumnType.Bytes:
                    type = "blob";
                    break;
                case ColumnType.Integer:
                    type = "integer";
                    break;
                case ColumnType.Float:
                    type = "real";
                    break;
                case ColumnType.DateTime:
                    type = "datetime";
                    break;
                case ColumnType.VarChar:
                    type = TextUtility.Merge("varchar(", length, ")");
                    break;
                case ColumnType.VarChar191:
                    type = TextUtility.Merge("varchar(191)");
                    break;
                case ColumnType.VarCharMax:
                    type = TextUtility.Merge("varchar(255)");
                    break;
                case ColumnType.Text:
                    type = TextUtility.Merge("text");
                    break;
                case ColumnType.NVarChar:
                    type = TextUtility.Merge("nvarchar(", length, ")");
                    break;
                case ColumnType.NVarChar191:
                    type = TextUtility.Merge("nvarchar(191)");
                    break;
                case ColumnType.NVarCharMax:
                    type = TextUtility.Merge("nvarchar(255)");
                    break;
                case ColumnType.NText:
                    type = TextUtility.Merge("ntext");
                    break;
                default:
                    return TextUtility.Empty;
            }
            return TextUtility.Merge("[", column.Field, "] ", type);
        }

        /// <summary>创建参数。</summary>
        /// <exception cref="ArgumentNullException"></exception>
        /// <exception cref="InvalidOperationException"></exception>
        public static SQLiteParameter Parameter(Parameter parameter)
        {
            if (parameter == null) throw new InvalidOperationException("参数无效。");
            return Parameter(parameter.Name, parameter.Type, parameter.Size, parameter.Value);
        }

        /// <summary>创建参数。</summary>
        public static SQLiteParameter Parameter(string name, ColumnType type, int size, object value)
        {
            var n = TextUtility.Trim(name);
            if (TextUtility.IsBlank(n)) return null;

            var t = GetColumnTypeName(type);

            var s = size;
            switch (type)
            {
                case ColumnType.VarChar:
                    s = NumberUtility.Restrict(s, 0, 8000);
                    break;
                case ColumnType.NVarChar:
                    s = NumberUtility.Restrict(s, 0, 4000);
                    break;
                case ColumnType.VarChar191:
                case ColumnType.NVarChar191:
                    s = NumberUtility.Restrict(s, 0, 191);
                    break;
                case ColumnType.VarCharMax:
                case ColumnType.NVarCharMax:
                    s = NumberUtility.Restrict(s, 0, 255);
                    break;
                default:
                    s = 0;
                    break;
            }

            var v = value;
            if (v is string && v != null && s > 0)
            {
                v = TextUtility.Left((string)v, s);
            }

            var p = new SQLiteParameter();
            p.ParameterName = n;
            p.TypeName = t;
            p.Value = v ?? DBNull.Value;
            if (s > 0) p.Size = s;
            return p;
        }

        /// <summary>创建参数。</summary>
        public static IDbDataParameter Parameter(string field, DbType type, int size, object value)
        {
            var p = new SQLiteParameter();
            p.ParameterName = field;
            p.DbType = type;
            p.Size = size;
            p.Value = value ?? DBNull.Value;
            return p;
        }

        /// <summary>创建参数。</summary>
        public static IDbDataParameter Parameter(string field, DbType type, object value)
        {
            var p = new SQLiteParameter();
            p.ParameterName = field;
            p.DbType = type;
            p.Value = value ?? DBNull.Value;
            return p;
        }

        #endregion

    }

}

